#include "texture.h"
#include "rendercontext.h"

#include "../debug.h"


#ifdef HAS_D3D
void
Epsilon3D::Direct3D::Texture2D::bind (Epsilon3D::Texture::Unit unit)
{
  LPDIRECT3DDEVICE9 dev = e3dDirect3D::GetDev ();

  Assert (unit > Epsilon3D::Texture::InvalidUnit &&
          unit < Epsilon3D::Texture::NumTextureUnits,
            _T ("Invalid texture unit"));

  dev->SetSamplerState (unit - 1, D3DSAMP_MAGFILTER, D3DTEXF_ANISOTROPIC);
  dev->SetSamplerState (unit - 1, D3DSAMP_MINFILTER, D3DTEXF_ANISOTROPIC);
  dev->SetSamplerState (unit - 1, D3DSAMP_MIPFILTER, D3DTEXF_ANISOTROPIC);
  dev->SetSamplerState (unit - 1, D3DSAMP_ADDRESSU,  D3DTADDRESS_WRAP);
  dev->SetSamplerState (unit - 1, D3DSAMP_ADDRESSV,  D3DTADDRESS_WRAP);

  dev->SetTexture (unit - 1, tex_handle);
}

void
Epsilon3D::Direct3D::Texture2D::setup (Epsilon3D::Texture::Unit unit)
{
  LPDIRECT3DDEVICE9 dev = e3dDirect3D::GetDev ();

  Assert (unit > Epsilon3D::Texture::InvalidUnit &&
          unit < Epsilon3D::Texture::NumTextureUnits,
            _T ("Invalid texture unit"));

  dev->SetSamplerState (unit - 1, D3DSAMP_MAGFILTER, D3DTEXF_ANISOTROPIC);
  dev->SetSamplerState (unit - 1, D3DSAMP_MINFILTER, D3DTEXF_ANISOTROPIC);
  dev->SetSamplerState (unit - 1, D3DSAMP_MIPFILTER, D3DTEXF_ANISOTROPIC);
  dev->SetSamplerState (unit - 1, D3DSAMP_ADDRESSU,  D3DTADDRESS_WRAP);
  dev->SetSamplerState (unit - 1, D3DSAMP_ADDRESSV,  D3DTADDRESS_WRAP);
}


void
Epsilon3D::Direct3D::Texture2D::enable (void)
{
}

void
Epsilon3D::Direct3D::Texture2D::disable (void)
{
}
#endif


void
Epsilon3D::OpenGL::Texture2D::bind (Epsilon3D::Texture::Unit unit)
{
  Assert (unit > Epsilon3D::Texture::InvalidUnit &&
          unit < Epsilon3D::Texture::NumTextureUnits,
            _T ("Invalid texture unit"));

  if (glIsTexture (tex_handle)) {
    glBindTexture (GL_TEXTURE_2D, tex_handle);
  } else {
    disable ();
    //glDisable (GL_TEXTURE_2D);
  }
}

void
Epsilon3D::OpenGL::Texture2D::setup (Epsilon3D::Texture::Unit unit)
{
  Assert (unit > Epsilon3D::Texture::InvalidUnit &&
          unit < Epsilon3D::Texture::NumTextureUnits,
            _T ("Invalid texture unit"));

  glTexParameteri (GL_TEXTURE_2D,  GL_TEXTURE_WRAP_S,     GL_REPEAT/*tex_wrap_s*/);
  glTexParameteri (GL_TEXTURE_2D,  GL_TEXTURE_WRAP_T,     GL_REPEAT/*tex_wrap_t*/);

  if (mipmaps) {
    glTexParameteri (GL_TEXTURE_2D,  GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri (GL_TEXTURE_2D,  GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
  } else {
    glTexParameteri (GL_TEXTURE_2D,  GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri (GL_TEXTURE_2D,  GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  }
}


void
Epsilon3D::OpenGL::Texture2D::enable (void)
{
  glEnable (GL_TEXTURE_2D);
}

void
Epsilon3D::OpenGL::Texture2D::disable (void)
{
  glDisable (GL_TEXTURE_2D);
}

/*
#include "..\..\Epsilon\smart_pointer.h"
#include "..\..\Epsilon\t_array.h"
#include "..\..\Epsilon\object.h"

using namespace Epsilon;

class e3dRenderState : public Object
{
public:
  virtual ~e3dRenderState (void);

  enum {
    ALPHA,
    CULL,
    DITHER,
    FOG,
    MATERIAL,
    SHADE,
    WIREFRAME,
    ZBUFFER,

    MAX_STATES
  };

  virtual int GetStateType (void) const = 0;
  static Pointer<e3dRenderState> Default [MAX_STATES];

};
*/

typedef unsigned int uint;

#undef log2
static uint
log2 (uint val)
{
  uint r = 0;

  while (val >>= 1)
    r++;

  return r;
}


/// Convert (and scale, if needed) an RGB image to an RGBA image.
///
///  * Fills the alpha channel with 0xff.
static void 
ScaleRGB (const void* src,       void* dst,
          int         src_width, int   src_height,
          int         dst_width, int   dst_height,
          int         bpp,       int   pitch = -1)
{
  unsigned char *d, *s;
  int i, j,
      x, y,
      c;

  float ratio_w = (float) src_width  / dst_width;
  float ratio_h = (float) src_height / dst_height;

  if (pitch == -1)
    pitch = dst_width * bpp;

  if (! ((src_width && src_height) &&
         (dst_width && dst_height))) {
    Assert (false, _T ("Invalid dimensions passed to ScaleRGB..."));
    return;
  }

  for (y = 0; y < dst_height; y++) {
    j = (int)(y * ratio_h);
    d = (unsigned char *)dst + pitch * y;

    for (x = 0; x < dst_width; x++) {
      i = (int)(x * ratio_w);
      s = ((unsigned char *)src) + ((j * src_width) + i) * bpp;

      for (c = 0; c < bpp; c++) {
        *d++ = *s++;
      }
    }
  }
}


static void 
ScaleRGBA (const void* src,       void* dst,
           int         src_width, int   src_height,
           int         dst_width, int   dst_height,
           int         bpp,       int   pitch = -1)

// *** TODO: ColorRGB    alpha_mask = 0x000000
{
  unsigned char *d, *s;
  int i, j,
      x, y,
      c;

  bool add_byte;

  float ratio_w = (float) src_width  / dst_width;
  float ratio_h = (float) src_height / dst_height;

  if (pitch == -1)
    pitch = dst_width * 4;//bpp;

  if (! ((src_width && src_height) &&
         (dst_width && dst_height))) {
    Assert (false, _T ("Invalid dimensions passed to ScaleRGBA..."));
    return;
  }

  Assert (bpp >= 3 && bpp <= 4, _T ("Invalid pixel size for ScaleRGBA..."));

  // Add a byte if bpp == 3
  add_byte = (bpp == 3);

  for (y = 0; y < dst_height; y++) {
    j = (int)(y * ratio_h);
    d = (unsigned char *)dst + pitch * y;

    for (x = 0; x < dst_width; x++) {
      i = (int)(x * ratio_w);
      s = ((unsigned char *)src) + ((j * src_width) + i) * bpp;

      // Remove the stupid pink alpha mask
      if (*(s + 2) == 0xff &&
          *(s + 1) == 0x00 &&
          *(s)     == 0xff) {
        *(s + 2) = 0x00;
        *(s + 1) = 0x00;
        *(s)     = 0x00;
      }

      for (c = 0; c < bpp; c++) {
        *d++ = *s++;
      }

      // This is a 3 bpp image, the alpha component is either ON or OFF,
      //  depending on the value of the corresponding RGB pixel.
      if (add_byte) {
        if (*(s - 3) == 0x00 &&
            *(s - 2) == 0x00 &&
            *(s - 1) == 0x00) {
          *d++ = 0x00;  // Transparent
        } else {
          *d++ = 0xff;  // Opaque
        }
      }
    }
  }
}

#include "image.h"

#ifdef HAS_D3D
bool
Epsilon3D::Direct3D::Texture2D::create (eImage* img, bool mipmap)
{
  Assert (img != NULL, _T ("Invalid image"));

  if (! img)
    return false;

  int        flags  = NULL;
  _D3DFORMAT format = D3DFMT_UNKNOWN;

  // Must be a power-of-two
  int width  = 1 << log2 ((uint) img->Width  ());
  int height = 1 << log2 ((uint) img->Height ());

  if (width  < img->Width  ()) width  = 2 << log2 ((uint) img->Width  ());
  if (height < img->Height ()) height = 2 << log2 ((uint) img->Height ());

  if (width  != img->Width  () ||
      height != img->Height ()) {
    // Rescale
  }

  if (mipmap)
    flags |= D3DUSAGE_AUTOGENMIPMAP;

  if (img->Bpp () == 3) {
    if (img->Format () == Epsilon::Image::Formats::RGB)
      format = D3DFMT_X8R8G8B8;
    if (img->Format () == Epsilon::Image::Formats::BGR)
      format = D3DFMT_X8R8G8B8;
      //format = D3DFMT_X8B8G8R8;
  } else if (img->Bpp () == 4) {
    if (img->Format () == Epsilon::Image::Formats::RGBA)
      format = D3DFMT_A8R8G8B8;
  }
  
  if (format == D3DFMT_UNKNOWN) {
    Assert (format != D3DFMT_UNKNOWN, _T ("Invalid texture bit depth"));

    return false;
  }

  if (FAILED (D3DXCreateTexture ( e3dDirect3D::GetDev (),
                                    width, height,
                                      D3DX_DEFAULT,
                                        flags,
                                          format,
                                            D3DPOOL_MANAGED,
                                              &tex_handle))) {
    return false;
  } else {
    D3DSURFACE_DESC surfDesc;
    tex_handle->GetLevelDesc (0, &surfDesc);

    D3DLOCKED_RECT lockedRect;
    tex_handle->LockRect (0, &lockedRect, NULL, 0);
    {
      //if (type == Underlight::VisualEffect) {
      if (img->Bpp () == 4) {
        ScaleRGBA ( img->Data (),   lockedRect.pBits,
                    img->Width (),  img->Height (),
                    surfDesc.Width, surfDesc.Height,
                    4,              lockedRect.Pitch );
      } else {
        ScaleRGBA ( img->Data (),   lockedRect.pBits,
                    img->Width (),  img->Height (),
                    surfDesc.Width, surfDesc.Height,
                    3,              lockedRect.Pitch );
      }
    }
    tex_handle->UnlockRect (0);
  }

  mipmaps = mipmap;

  return true;
}

void
Epsilon3D::Direct3D::Texture2D::destroy (void)
{
  LPDIRECT3DDEVICE9 dev = e3dDirect3D::GetDev ();

  if (tex_handle != NULL) {
    tex_handle->Release ();
    tex_handle = NULL;
  }
}
#endif


///#include <GL/glu.h>

bool
Epsilon3D::OpenGL::Texture2D::create (eImage* img, bool mipmap)
{
  Assert (img != NULL, _T ("Invalid image"));

  if (! img)
    return false;

  byte* data = (byte *)img->Data ();

int width = img->Width ();
int height = img->Height ();
/*
  // Must be a power-of-two
  int width  = 1 << log2 ((uint) img->Width  ());
  int height = 1 << log2 ((uint) img->Height ());

  if (width  < img->Width  ()) width  = 2 << log2 ((uint) img->Width  ());
  if (height < img->Height ()) height = 2 << log2 ((uint) img->Height ());

  if (width  != img->Width  () ||
      height != img->Height ()) {
    data = new byte [width * height * img->Bpp ()];
    
    if (img->Bpp () == 4) {
      ScaleRGBA ( img->Data (),   data,
                  img->Width (),  img->Height (),
                  width,          height,
                  4,              -1 );
    } else {
      ScaleRGB ( img->Data (),   data,
                 img->Width (),  img->Height (),
                 width,          height,
                 3,              -1 );
    }

    printf ("Rescaled OpenGL Texture: (%dx%d => %dx%d)\n", img->Width (), img->Height (),
                                                           width,         height);
    // Rescale
  }
*/

  glGenTextures (1, &tex_handle);

  glBindTexture (GL_TEXTURE_2D, tex_handle);

  int texFormat = GL_RGB;

  if (img->Bpp () == 4)
    texFormat = GL_RGBA;

  if (mipmap) {
    gluBuild2DMipmaps (GL_TEXTURE_2D, texFormat, width,
                                                 height,
                                      texFormat,
                       GL_UNSIGNED_BYTE,         data);
  } else {
    glTexImage2D (GL_TEXTURE_2D, 0, texFormat, width,
                                               height,
                                 0, texFormat,
                  GL_UNSIGNED_BYTE,            data);
  }

#if 0
    /* Add future support to change these parameters... */
    glTexParameteri (GL_TEXTURE_2D,  GL_TEXTURE_WRAP_S,     GL_REPEAT/*tex_wrap_s*/);
    glTexParameteri (GL_TEXTURE_2D,  GL_TEXTURE_WRAP_T,     GL_REPEAT/*tex_wrap_t*/);
    glTexParameteri (GL_TEXTURE_2D,  GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri (GL_TEXTURE_2D,  GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexEnvf       (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,   GL_MODULATE);
#endif

  if (data != (byte *)img->Data ())
    delete [] data;

  mipmaps = mipmap;

  return true;
}

void
Epsilon3D::OpenGL::Texture2D::destroy (void)
{
  if (glIsTexture (tex_handle))
    glDeleteTextures (1, &tex_handle);
}
